#include <assert.h>
#include <errno.h>
#include <inttypes.h>
#include <limits.h>
#include <signal.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <unistd.h>

#ifdef USE_CAPSICUM
#include <sys/capsicum.h>
#endif

#include <err.h>
#include <sysexits.h>

#include "au.h"
#include "common.h"

#ifdef SIGINFO
#define IFINFO(x) x
#else
#define IFINFO(x)
#endif

typedef void (au_tone)(struct au *, void *, size_t);

#ifdef SIGINFO
static size_t info_out = 0;
#endif

#ifdef SIGINFO
static void siginfo(int);
#endif
static size_t au_putsample(struct au *, void *, uint32_t);
static au_tone square;
static au_tone sawtooth;
static au_tone triangle;

const struct {
	const char *str;
	au_tone *func;
} ftab[] = {
	{ "square", &square },
	{ "sq", &square },
	{ "sawtooth", &sawtooth },
	{ "saw", &sawtooth },
	{ "triangle", &triangle },
	{ "tri", &triangle },
	{ NULL, NULL }
};

int
main(int argc, char *argv[])
{
#ifdef USE_CAPSICUM
	cap_rights_t rights;
#endif
	struct au au = AU_INITIALIZER;
	au_tone *tone = square;
	void *b;
	char *p;
	ssize_t r;
	size_t ssize;
	size_t bsize;
	size_t samples;
	size_t n = 0;
	size_t i;
	double dfreq = 440;
	double l = 0;
	unsigned long ul;
	int raw = 0;
	int c;

	close(STDIN_FILENO);
#ifdef USE_CAPSICUM
	if (cap_enter() == -1 && errno != ENOSYS)
		err(EX_OSERR, "Could not enter capibility mode");
	cap_rights_init(&rights, CAP_WRITE);
	if (cap_rights_limit(STDOUT_FILENO, &rights) == -1 &&
	    errno != ENOSYS)
		err(EX_OSERR, "Could not limit the standard output");
	if (cap_rights_limit(STDERR_FILENO, &rights) == -1 &&
	    errno != ENOSYS)
		err(EX_OSERR, "Could not limit the standard error");
#endif
#ifdef USE_PLEDGE
	pledge("stdio", NULL);
#endif
	au.au_chan = 1;
	while ((c = getopt(argc, argv, "Rc:e:f:l:n:r:t:")) != -1)
		switch (c) {
		case 'R':
			raw = 1;
			break;
		case 'c':
			au.au_chan = strtou32(optarg);
			break;
		case 'e':
			if ((au.au_enc = au_strenc(optarg)) == 0)
				err(EX_USAGE, "Bad encoding %s",
				    optarg);
			break;
		case 'f':
			errno = 0;
			dfreq = strtod(optarg, &p);
			if (dfreq < 0)
				errno = ERANGE;
			if (errno != 0 || p == optarg || *p != '\0')
				err(EX_USAGE, "Invalid frequency %s",
				    optarg);
			break;
		case 'l':
			errno = 0;
			l = strtod(optarg, &p);
			if (l < 0)
				errno = ERANGE;
			if (errno != 0 || p == optarg || *p != '\0')
				err(EX_USAGE, "Invalid time %s",
				    optarg);
			n = 0;
			break;
		case 'n':
			errno = 0;
			ul = strtoul(optarg, &p, 0);
			if (ul > SIZE_MAX)
				errno = ERANGE;
			if (errno != 0 || p == optarg)
				err(EX_USAGE, "Invalid number %s",
				    optarg);
			n = ul;
			break;
		case 'r':
			au.au_rate = strtou32(optarg);
			break;
		case 't':
			for (i = 0; ftab[i].str != NULL; i++)
				if (strcasecmp(optarg, ftab[i].str) ==
				    0)
					break;
			if (ftab[i].str == NULL)
				errx(EX_USAGE, "Invalid type %s",
				    optarg);
			tone = ftab[i].func;
			break;
		default:
			goto usage;
		}
	if (optind != argc)
		goto usage;
#ifdef SIGINFO
	(void)signal(SIGINFO, &siginfo);
#endif
	samples = (size_t)((double)au.au_rate / dfreq);
	ssize = au_ssize(&au);
	bsize = samples * ssize;
	if ((b = malloc(bsize)) == NULL)
		err(EX_OSERR, "Could not allocate the buffer");
	if (n == 0)
		n = (size_t)(l * (double)au.au_rate / (double)samples +
		    0.5);
	if (!raw) {
		if (n != 0)
			au.au_size = n * bsize > UINT32_MAX ?
			    UINT32_MAX : n * bsize;
		(void)au_puthdr(&au, b);
		if (nwrite(STDOUT_FILENO, b, AU_SIZE) != AU_SIZE)
			err(EX_IOERR, "Could not write audio header");
	}
	tone(&au, b, samples);
	if (n == 0)
		while ((r = write(STDOUT_FILENO, b, bsize)) > 0)
			IFINFO(info_out += r / ssize);
	else
		for (; n > 0; n--) {
			if (write(STDOUT_FILENO, b, bsize) != bsize)
				err(EX_IOERR, "Write error");
			IFINFO(info_out += bsize / ssize);
		}
	free(b);
	return (0);
usage:
	(void)fprintf(stderr, "usage: autone [-R] [-c channels] "
	    "[-e encoding] [-f frequnecy]\n"
	    "              [-l length] [-n cycles] [-r rate] "
	    "[-t type]\n");
	return (EX_USAGE);
}

#ifdef SIGINFO
void
siginfo(int sig)
{
	/* autone: XXXXXX samples out */
	char b[8 + 6 + 13];
	size_t i = 0;

	(void)memcpy(b + i, "autone: ", 8);
	i += 8;
	i += szwrite(b + i, info_out, NULL, NULL);
	if (b[i - 1] == 'B')
		i -= 2;
	(void)memcpy(b + i, " samples out\n", 13);
	i += 13;
	(void)write(STDERR_FILENO, b, i);
}
#endif

size_t
au_putsample(struct au *au, void *buf, uint32_t v)
{
	const uint32_t end = 1;
	char tb[sizeof(double)];
	uint8_t *b = buf;
	double d;
	float f;
	uint32_t l;
	uint32_t i;

	assert(au != NULL && buf != NULL);
	l = au_vsize(au);
	switch (au->au_enc) {
	case AU_PCM8:
		/* FALLTHROUGH */
	case AU_PCM16:
		/* FALLTHROUGH */
	case AU_PCM24:
		/* FALLTHROUGH */
	case AU_PCM32:
		for (i = 0; i < l; i++)
			tb[i] = (v >> (sizeof(v) - i - 1) * 8) & 0xFF;
		break;
	case AU_FLOAT32:
		f = (v >> 31) ? -(float)(~v + 1) / (float)PCM32_MIN :
		    (float)v / (float)PCM32_MAX;
		if (*(char *)&end)
			for (i = 0; i < sizeof(d); i++)
				tb[i] = ((char *)&f)[sizeof(f) - i - 1];
		else
			(void)memcpy(tb, &f, sizeof(f));
		break;
	case AU_FLOAT64:
		d = (v >> 31) ? -(double)(~v + 1) / (double)PCM32_MIN :
		    (double)v / (double)PCM32_MAX;
		if (*(char *)&end)
			for (i = 0; i < sizeof(d); i++)
				tb[i] = ((char *)&d)[sizeof(d) - i - 1];
		else
			(void)memcpy(tb, &d, sizeof(d));
		break;
	default:
		errx(EX_USAGE, "Unsupported encoding %"PRIu32,
		    au->au_enc);
	}
	for (i = 0; i < au->au_chan; i++, b += l)
		(void)memcpy(b, tb, l);
	return (l * au->au_chan);
}

void
square(struct au *au, void *buf, size_t samples)
{
	uint8_t *b = buf;
	size_t i;

	assert(au != NULL && buf != NULL);
	for (i = 0; i < samples / 2; i++)
		b += au_putsample(au, b, PCM32_MIN);
	for (; i < samples; i++)
		b += au_putsample(au, b, PCM32_MAX);
}

void
sawtooth(struct au *au, void *buf, size_t samples)
{
	uint8_t *b = buf;
	size_t i;
	uint32_t t;
	uint32_t v;

	assert(au != NULL && buf != NULL);
	t = UINT32_MAX / samples;
	for (i = 0, v = 0; i < samples; i++, v += t)
		b += au_putsample(au, b, v);
}

void
triangle(struct au *au, void *buf, size_t samples)
{
	uint8_t *b = buf;
	size_t a;
	size_t i;
	uint32_t t;
	uint32_t t0;
	uint32_t t1;
	uint32_t v;

	assert(au != NULL && buf != NULL);
	a = samples % 4;
	t0 = UINT32_MAX / samples * 2;
	t1 = UINT32_MAX / (samples + 4) * 2;
	t = t0;
	for (i = 0, t = a == 0 ? t0 : t1, a -= a != 0, v = 0;
	    i < samples && v <= PCM32_MAX; i++, v += t)
		b += au_putsample(au, b, v);
	for (v = PCM32_MAX, t = a == 0 ? t0 : t1, a -= a != 0;
	     i < samples && (v -= t) <= PCM32_MAX; i++)
		b += au_putsample(au, b, v);
	for (v = UINT32_MAX, t = a == 0 ? t0 : t1, a -= a != 0;
	     i < samples && (v -= t) >= PCM32_MIN; i++)
		b += au_putsample(au, b, v);
	for (v = PCM32_MIN, t = a == 0 ? t0 : t1, a -= a != 0;
	     i < samples && (v += t) >= PCM32_MIN; i++)
		b += au_putsample(au, b, v);
}
